package org.hamster.common;

import java.util.stream.Stream;

/**
 * A class for handling IPv4 addresses and masks using stream parsing and bitwise operations.
 * Provides functionality to validate IP addresses and check if an IP is within a given range.
 */
public class IPv4Mask implements IPMaskable {
    private final int networkAddress;
    private final int subnetMask;
    private final int prefixLength;

    /**
     * Constructs an IPv4Mask from a string that can be either an IP address or an IP range in CIDR notation.
     *
     * @param ipString the IP address or IP range in CIDR notation (e.g. "192.168.1.1" or "192.168.1.0/24")
     * @throws IllegalArgumentException if the input string is not a valid IP address or CIDR notation
     */
    public IPv4Mask(String ipString) {
        if (ipString == null || ipString.isEmpty()) {
            throw new IllegalArgumentException("IP string cannot be null or empty");
        }

        if (ipString.contains("/")) {
            // CIDR notation
            String[] parts = ipString.split("/");
            if (parts.length != 2) {
                throw new IllegalArgumentException("Invalid CIDR notation: " + ipString);
            }

            String ipPart = parts[0];
            int prefixLength;
            try {
                prefixLength = Integer.parseInt(parts[1]);
            } catch (NumberFormatException e) {
                throw new IllegalArgumentException("Invalid prefix length in CIDR notation: " + parts[1]);
            }

            if (!IPValidationUtil.isValidIPv4(ipPart)) {
                throw new IllegalArgumentException("Invalid IP address in CIDR notation: " + ipPart);
            }

            if (prefixLength < 0 || prefixLength > 32) {
                throw new IllegalArgumentException("Prefix length must be between 0 and 32, got: " + prefixLength);
            }

            int ip = parseIpWithStream(ipPart);
            // Create subnet mask with all 1s for network part and 0s for host part
            subnetMask = prefixLength == 0 ? 0 : (~0) << (32 - prefixLength);
            networkAddress = ip & subnetMask;
            this.prefixLength = prefixLength;
        } else {
            // Single IP address
            if (!IPValidationUtil.isValidIPv4(ipString)) {
                throw new IllegalArgumentException("Invalid IP address: " + ipString);
            }

            networkAddress = parseIpWithStream(ipString);
            subnetMask = ~0; // All bits set to 1 (255.255.255.255)
            this.prefixLength = 32;
        }
    }

    /**
     * Parses an IPv4 address string to an integer using stream operations.
     *
     * @param ip the IP address string
     * @return the integer representation of the IP address
     */
    private static int parseIpWithStream(String ip) {
        return Stream.of(ip.split("\\."))
                .mapToInt(Integer::parseInt)
                .reduce(0, (result, octet) -> (result << 8) + octet);
    }

    /**
     * Converts an integer value to an IPv4 address string.
     *
     * @param ip the integer representation of the IP address
     * @return the IP address in dotted-decimal notation
     */
    private static String intToIp(int ip) {
        return ((ip >> 24) & 0xFF) + "." + ((ip >> 16) & 0xFF) + "." +
                ((ip >> 8) & 0xFF) + "." + (ip & 0xFF);
    }

    /**
     * Checks if the given IP string is a valid IPv4 address without using regular expressions.
     *
     * @param ip the IP address string to validate
     * @return true if the IP is valid, false otherwise
     */
    public static boolean isValidIPv4(String ip) {
        return IPValidationUtil.isValidIPv4(ip);
    }

    /**
     * Checks if the given IP address is within the range represented by this IPv4Mask using bitwise operations.
     *
     * @param ip the IP address to check
     * @return true if the IP is valid and within range, false otherwise
     */
    @Override
    public boolean inRange(String ip) {
        if (!IPValidationUtil.isValidIPv4(ip)) {
            return false;
        }

        int ipInt = parseIpWithStream(ip);
        // For a network with subnet mask, an IP is in range if:
        // (IP & subnetMask) == networkAddress
        return (ipInt & subnetMask) == networkAddress;
    }

    /**
     * Returns the network address as a string.
     *
     * @return the network IP address
     */
    @Override
    public String getStartIp() {
        return intToIp(networkAddress);
    }

    /**
     * Returns the broadcast address as a string.
     *
     * @return the broadcast IP address
     */
    @Override
    public String getEndIp() {
        // For a subnet mask, the broadcast address is networkAddress | ~subnetMask
        int broadcastAddress = subnetMask == ~0 ? networkAddress : networkAddress | ~subnetMask;
        return intToIp(broadcastAddress);
    }

    @Override
    public String toString() {
        if (subnetMask == ~0) { // Single IP address (255.255.255.255)
            return intToIp(networkAddress);
        } else {
            return intToIp(networkAddress) + "/" + prefixLength;
        }
    }
}